#pragma once
#include "Image.hpp"

//For drawing element onto image

namespace zzz{

// cannot be put into the class, otherwise cannot be implement in cpp
void RasterizeLine(int r0, int c0, int r1, int c1, vector<Vector2i> &res);

template<typename T>
class ImageDrawer
{
public:
  ImageDrawer(Image<T> &img):img_(img){}
  Image<T> &img_;

  void DrawPoint(const T &p, int pos, float alpha=1);
  void DrawPoint(const T &p, int r, int c, float alpha=1);
  void DrawLine(const T &p, double r0, double c0, double r1, double c1, float alpha=1);
  void DrawCircle(const T &p, double r0, double c0, double radius, float alpha=1);
  void DrawBox(const T &p, double r0, double c0, double r1, double c1, float alpha=1);
  void DrawCross(const T &p, double r0, double c0, double len, float alpha=1);

  void FillBox(const T &p, double r0, double c0, double r1, double c1, float alpha=1);
  void FillCircle(const T &p, double r0, double c0, double radius, float alpha=1);
};


template<typename T>
void ImageDrawer<T>::DrawPoint(const T &p, int pos, float alpha)
{
  if (!Within(0,pos,(int)img_.size()-1)) return;
  img_.at(pos)=p*alpha+img_.at(pos)*(1.0f-alpha);
}

template<typename T>
void ImageDrawer<T>::DrawPoint(const T &p, int r, int c, float alpha)
{
  if (!img_.IsInside(Vector2i(r,c))) return;
  img_.At(r,c)=p*alpha+img_.At(r,c)*(1.0f-alpha);
}

template<typename T>
void ImageDrawer<T>::DrawLine(const T &p, double r0, double c0, double r1, double c1, float alpha)
{
  int ir0=int(floor(r0)), ic0=int(floor(c0));
  int ir1=int(floor(r1)), ic1=int(floor(c1));
  vector<Vector2i> line;
  RasterizeLine(ir0,ic0,ir1,ic1,line);
  for (zuint i=0; i<line.size(); i++)
    DrawPoint(p,line[i][0],line[i][1],alpha);
}

template<typename T>
void ImageDrawer<T>::DrawCircle(const T &p, double r0, double c0, double radius, float alpha)
{
  int ir0=int(floor(r0)), ic0=int(floor(c0));
  int sr=Round(ir0+radius*cos(0.0)),sc=Round(ic0+radius*sin(0.0));
  for (int i=1; i<=20; i++)
  {
    double angle=i/20.0*PI*2;
    int sr1=Round(ir0+radius*cos(angle)),sc1=Round(ic0+radius*sin(angle));
    DrawLine(p,sr,sc,sr1,sc1,alpha);
    sr=sr1;sc=sc1;
  }
}

template<typename T>
void ImageDrawer<T>::DrawBox(const T &p, double r0, double c0, double r1, double c1, float alpha)
{
  DrawLine(p,r0,c0,r1,c0,alpha);
   DrawLine(p,r1,c0,r1,c1,alpha);
   DrawLine(p,r1,c1,r0,c1,alpha);
   DrawLine(p,r0,c1,r0,c0,alpha);
}

template<typename T>
void ImageDrawer<T>::DrawCross(const T &p, double r0, double c0, double len, float alpha)
{
  DrawLine(p,r0,c0,r0+len,c0,alpha);
  DrawLine(p,r0,c0,r0,c0+len,alpha);
  DrawLine(p,r0,c0,r0-len,c0,alpha);
  DrawLine(p,r0,c0,r0,c0-len,alpha);
}

template<typename T>
void ImageDrawer<T>::FillBox(const T &p, double r0, double c0, double r1, double c1, float alpha)
{
  if (r0<r1)
    for (int r=r0; r<=r1; r++)
      DrawLine(p,r,c0,r,c1,alpha);
  else
    for (int r=r1; r<=r0; r++)
      DrawLine(p,r,c0,r,c1,alpha);
}

//there should be a faster way to do this
//fill it row by row
//for each row, find the start point and end point
template<typename T>
void ImageDrawer<T>::FillCircle(const T &p, double r0, double c0, double radius, float alpha)
{
  int ir0=int(floor(r0)), ic0=int(floor(c0));
  for (int r=ir0-radius; r<ir0; r++)
  {
//     if (r<0 || r>=(int)img_.Rows()) continue;
    double halfwidth=Sqrt<double>(radius*radius-(r-ir0)*(r-ir0));
    DrawLine(p,r,Round(ic0-halfwidth),r,Round(ic0+halfwidth),alpha);
    DrawLine(p,2*r0-r,Round(ic0-halfwidth),2*r0-r,Round(ic0+halfwidth),alpha);
  }
  DrawLine(p,ir0,ic0-radius,ir0,ic0+radius,alpha);
}

}
